/*
 * File:   file_system.cpp
 * Author: dolphinigle
 *
 * Created on December 3, 2011, 3:12 PM
 */

#include "file_system.h"
#include "util.h"

#include <cassert>
#include <cstdio>
#include <list>
#include <string>

using std::string;
using std::make_pair;
using std::list;

FileSystem::FileSystem(string filename) {
  filename_ = filename;

  handle_ = fopen(filename.c_str(), "r+");
  data_length_ = ReadInt(handle_);
  bitmask_offset_ = kMetadataSize;
  data_offset_ = kMetadataSize + data_length_;

  // Reading directories
  int dir_cnt = ReadInt(handle_);
  for (int i = 0; i < dir_cnt; ++i) {
    int namelen = ReadInt(handle_);
    string nama = "";
    char ch;
    for (int j = 0; j < namelen; ++j) {
      ch = (char)fgetc(handle_);
      nama += ch;
    }
    dirs_.push_back(nama);
  }

  // Reading files.
  int file_cnt = ReadInt(handle_);
  for (int i = 0; i < file_cnt; ++i) {
    int namelen = ReadInt(handle_);
    string nama = "";
    char ch;
    for (int j = 0; j < namelen; ++j) {
      ch = (char)fgetc(handle_);
      nama += ch;
    }
    int offset = ReadInt(handle_);
    int len = ReadInt(handle_);
    files_.push_back(make_pair(nama, make_pair(offset, len)));
  }
}
string FileSystem::Close() {
  // We have to rewrite the metadata.
  fseek(handle_, 0, SEEK_SET);
  WriteInt(handle_, data_length_);

  // Writing directories.
  WriteInt(handle_, dirs_.size());

  for (DIt it = dirs_.begin(); it != dirs_.end(); ++it) {
    WriteInt(handle_, it->size());
    for (size_t i = 0; i < it->size(); ++i) {
      fputc((*it)[i], handle_);
    }
  }

  // Writing files
  WriteInt(handle_, files_.size());

  for (FIt it = files_.begin(); it != files_.end(); ++it) {
    WriteInt(handle_, it->first.size());
    for (size_t i = 0; i < it->first.size(); ++i) {
      fputc(it->first[i], handle_);
    }
    WriteInt(handle_, it->second.first);
    WriteInt(handle_, it->second.second);
  }

  // All done.
  fclose(handle_);

  return filename_ + " has been written";
}

string FileSystem::Format(string filename, size_t size) {
  if (size <= kMetadataSize) {
    return "Minimum size is " + IntToString(kMetadataSize);
  }

  FILE* fsx = fopen(filename.c_str(), "w");
  char buffer = '0';
  for (int i = 0; i < size; ++i) {
    fputc('0', fsx);
  }
  fclose(fsx);

  FileSystem init_fs(filename);
  init_fs.handle_ = fopen(filename.c_str(), "r+");
  init_fs.filename_ = filename;
  init_fs.data_length_ = (size - kMetadataSize) / 2;
  init_fs.bitmask_offset_ = kMetadataSize;
  init_fs.data_offset_ = kMetadataSize + init_fs.data_length_;
  init_fs.dirs_.push_back("/");

  return init_fs.Close();
}

FileSystem::FileSystem() {}

string FileSystem::IsExist(string path) {
  if (IsDirectoryName(path)) {
    for (DIt it = dirs_.begin(); it != dirs_.end(); ++it) {
      if (*it == path) {
        return "YES";
      }
    }
  } else {
    for (FIt it = files_.begin(); it != files_.end(); ++it) {
      if (it->first == path) {
        return "YES";
      }
    }
  }
  return "NO";
}
string FileSystem::Ls(string path) {
  string result = "";
  for (DIt it = dirs_.begin(); it != dirs_.end(); ++it) {
    if (GetFolder(*it) == path) {
      if (result != "") result += "\n";
      result += *it;
    }
  }
  for (FIt it = files_.begin(); it != files_.end(); ++it) {
    if (GetFolder(it->first) == path) {
      if (result != "") result += "\n";
      result += it->first + " " + IntToString(it->second.second) + "B";
    }
  }
  return result;
}
string FileSystem::Mkdir(string path) {
  dirs_.push_back(path);
  return "Directory " + path + " added";
}
string FileSystem::Rm(string path) {
  if (path == "/") {
    return "You cannot delete root of file systems";
  }
  if (IsFileName(path)) {
    for (FIt it = files_.begin(); it != files_.end(); ++it) {
      if (it->first == path) {
        // Frees the memory
        fseek(handle_, bitmask_offset_ + it->second.first, SEEK_SET);
        for (int i = 0; i < it->second.second; ++i) {
          fputc('0', handle_);
        }
        files_.erase(it);
        return path + " is removed";
      }
    }
    assert(false);
  }

  string res = "";
  for (FIt it = files_.begin(); it != files_.end();) {
    if (it->first.substr(0, path.size()) == path) {
      FIt delit = it;
      ++it;
      if (res != "") res += "\n";
      res += delit->first + " is removed";

      // Frees the memory
      fseek(handle_, bitmask_offset_ + delit->second.first, SEEK_SET);
      for (int i = 0; i < delit->second.second; ++i) {
        fputc('0', handle_);
      }

      files_.erase(delit);
    } else {
      ++it;
    }
  }

  for (DIt it = dirs_.begin(); it != dirs_.end();) {
    if (it->substr(0, path.size()) == path) {
      DIt delit = it;
      ++it;
      if (res != "") res += "\n";
      res += *it + " is removed";

      dirs_.erase(delit);
    } else {
      ++it;
    }
  }

  return res;
}
string FileSystem::Cp1(string path, string real_path) {
  if (IsFileName(path)) {
    FILE* fp = fopen(real_path.c_str(), "w");
    // find the file
    int offset = -1;
    int len = -1;
    for (FIt it = files_.begin(); it != files_.end(); ++it) {
      if (it->first == path) {
        offset = it->second.first;
        len = it->second.second;
        break;
      }
    }
    assert(offset != -1);
    fseek(handle_, data_offset_ + offset, SEEK_SET);
    for (int i = 0; i < len; ++i) {
      fputc((char)fgetc(handle_), fp);
    }
    fclose(fp);
    return path + " copied to " + real_path;
  } else {
    // Directory
    return "Copying directory is not supported";
  }
}
string FileSystem::Cp2(string realpath, string path) {

  if (IsFileName(path)) {
    FILE* fp = fopen(realpath.c_str(), "r");

    // Calculating length of file.
    fseek(fp, 0, SEEK_END);
    int length_of_file = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    // Finding a consecutive of at least length_of_file 0s.
    int position = 0;
    int len = 0;

    fseek(handle_, bitmask_offset_, SEEK_SET);
    for (int i = 0; i < data_length_; ++i) {
      char ch = (char)fgetc(handle_);
      if (ch == '1') {
        position = i+1;
        len = 0;
      } else {
        ++len;
        if (len >= length_of_file) {
          break;
        }
      }
    }

    if (len < length_of_file) {
      return "Not enough space to do copy";
    }

    // Marks the slots that are going to be filled.
    fseek(handle_, bitmask_offset_ + position, SEEK_SET);
    for (int i = 0; i < length_of_file; ++i) {
      fputc('1', handle_);
    }

    // Fills the slots
    fseek(handle_, data_offset_ + position, SEEK_SET);
    for (int i = 0; i < length_of_file; ++i) {
      char ch = (char)fgetc(fp);
      fputc(ch, handle_);
    }

    // Creates a new file entry
    files_.push_back(make_pair(path, make_pair(position, length_of_file)));

    return realpath + " has been copied to " + path;
  } else {
    // Directory
    return "Copying directory is not supported";
  }
}

string FileSystem::Cat(string path) {
  int offset = -1;
  int len = -1;
  for (FIt it = files_.begin(); it != files_.end(); ++it) {
    if (it->first == path) {
      offset = it->second.first;
      len = it->second.second;
      break;
    }
  }
  assert(offset != -1);
  fseek(handle_, data_offset_ + offset, SEEK_SET);
  string ret = "";
  for (int i = 0; i < len; ++i) {
    ret += (char)fgetc(handle_);
  }
  return ret;
}
string FileSystem::Status() {
  return "filename='" + filename_ + "' " +
         "number of files='" + IntToString(files_.size()) + "' " +
         "number of directories='" + IntToString(dirs_.size()) + "' " +
         "maximum data size='" + IntToString(data_length_) + "'";
}

void FileSystem::WriteInt(FILE* handle, int i) {
  string str = IntToString(i);

  while (str.size() < kIntegerLength) {
    str = "0" + str;
  }
  assert(str.size() == kIntegerLength);

  for (int i = 0; i < str.size(); ++i) {
    fputc(str[i], handle);
  }
}

int FileSystem::ReadInt(FILE* handle) {
  int value = 0;
  for (int i = 0; i < kIntegerLength; ++i) {
    value *= 10;
    char ch = (char)fgetc(handle_);
    value += (int)(ch - '0');
  }
  return value;
}

